home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / SourceCode / MiniExamples / AppKit / TextORama / EmacsText.m < prev    next >
Text File  |  1995-06-12  |  10KB  |  428 lines

  1. /* EmacsText.m
  2.  *
  3.  *  EmacsText is a subclass of Text which adds support for
  4.  * keyboard bindings commonly used by the emacs editor.
  5.  *
  6.  * You may freely copy, distribute, and reuse the code in this example.
  7.  * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
  8.  * fitness for any particular use.
  9.  *
  10.  * Written by:  Julie Zelenski
  11.  * Created:  Sept/91
  12.  */
  13.  
  14. #import "EmacsText.h"
  15.  
  16. @implementation EmacsText
  17.  
  18. /** This is the charCode offset for Control keys from Encoding Vectors Tech Doc **/
  19.  
  20. #define CONTROL_OFFSET (unsigned short)0x40
  21.  
  22.  
  23. /** Cursor Movement Commands **/
  24.  
  25. #define CTRL_A ('A'  - CONTROL_OFFSET)
  26. #define CTRL_B ('B'  - CONTROL_OFFSET)
  27. #define CTRL_E ('E'  - CONTROL_OFFSET)
  28. #define CTRL_F ('F'  - CONTROL_OFFSET)
  29. #define CTRL_N ('N'  - CONTROL_OFFSET)
  30. #define CTRL_P ('P'  - CONTROL_OFFSET)
  31. #define ALT_LESS ((unsigned short)0xa3)
  32. #define ALT_GREATER ((unsigned short) 0xb3)
  33. #define ALT_B ((unsigned short) 0xe5)
  34. #define ALT_F ((unsigned short) 0xa6)
  35.  
  36. /** Delete Commands  **/
  37.  
  38. #define CTRL_D ('D'  - CONTROL_OFFSET)
  39. #define CTRL_K ('K'  - CONTROL_OFFSET)
  40. #define CTRL_O ('O'  - CONTROL_OFFSET)
  41. #define CTRL_Y ('Y'  - CONTROL_OFFSET)
  42. #define ALT_D ((unsigned short) 0x44)
  43. #define ALT_H ((unsigned short) 0xe3)
  44.  
  45.  
  46.  
  47. typedef struct _sel
  48. {
  49.     unsigned short charCode;
  50.     SEL selector;
  51.     SEL positionSelector;
  52.     char *selectorString;
  53.     char *positionSelectorString;
  54. } SelectorItem;
  55.  
  56. static SelectorItem emacsMetaKeys[] = 
  57. {
  58. {ALT_B, 0, 0, "moveToPosition:", "positionForWordBegin"},
  59. {ALT_F, 0, 0, "moveToPosition:", "positionForWordEnd"},
  60. {ALT_LESS, 0, 0, "moveToPosition:", "positionForDocumentBegin"},
  61. {ALT_GREATER, 0, 0, "moveToPosition:", "positionForDocumentEnd"},
  62. {ALT_D, 0, 0, "deleteToPosition:", "positionForWordEnd"},
  63. {ALT_H, 0, 0, "deleteToPosition:", "positionForWordBegin"},
  64. {0}
  65. };
  66.  
  67. static SelectorItem emacsControlKeys[] = 
  68. {
  69. {CTRL_A, 0, 0, "moveToPosition:", "positionForLineBegin"},
  70. {CTRL_E, 0, 0, "moveToPosition:", "positionForLineEnd"},
  71. {CTRL_K, 0, 0, "deleteToLineEnd", 0},
  72. {CTRL_D, 0, 0, "deleteToPosition:", "nextPositionIfEmpty"},
  73. {CTRL_Y, 0, 0, "yank", 0},
  74. {0}
  75. };
  76.  
  77. unsigned short emacsFilter (unsigned short
  78.     charCode, int flags, unsigned short charSet)
  79. {
  80.     if (flags & NX_CONTROLMASK) 
  81.     {         
  82.     switch(charCode) {
  83.         case CTRL_F:
  84.         return NX_RIGHT;
  85.         case CTRL_B:
  86.             return NX_LEFT;
  87.         case CTRL_N:
  88.             return NX_DOWN;
  89.         case CTRL_P:
  90.             return NX_UP;
  91.         default: break;
  92.     }
  93.     } 
  94.     return NXEditorFilter(charCode, flags, charSet);
  95. }
  96.  
  97.  
  98. int GetPrevious(NXStream *s)
  99. {
  100.      int pos;
  101.      int ch;
  102.      
  103.      pos = NXTell(s);
  104.      if (pos <= 0) return EOF;
  105.      NXSeek(s, --pos, NX_FROMSTART);
  106.      ch = NXGetc(s);
  107.      NXUngetc(s);
  108.      return ch;
  109. }
  110.  
  111. // Complete the build of the selector tables
  112. +initialize
  113. {
  114.     SelectorItem *cur;
  115.  
  116.     for (cur = emacsMetaKeys; cur->charCode; cur++)
  117.     {
  118.     cur->selector = sel_getUid(cur->selectorString);
  119.     cur->positionSelector = sel_getUid(cur->positionSelectorString);
  120.     }
  121.  
  122.     for (cur = emacsControlKeys; cur->charCode; cur++)
  123.     {
  124.     cur->selector = sel_getUid(cur->selectorString);
  125.     cur->positionSelector = sel_getUid(cur->positionSelectorString);
  126.     }
  127.     return self;
  128. }
  129.  
  130. - (int)positionForLineBeginActual
  131. /* Not currently in use.  Looks for newline to find actual paragraph begin.
  132.  */
  133. {
  134.     NXStream *s = [self stream];
  135.     int pos;
  136.     int ch;
  137.     
  138.     if (spN.cp < 0) return 0; // Is this the right thing to do here?
  139.  
  140.     NXSeek(s, sp0.cp, NX_FROMSTART);
  141.     while (((ch = GetPrevious(s)) != EOF) && (ch != '\n'));
  142.     pos = NXTell(s);
  143.     if (ch != EOF) pos++;
  144.     return pos;
  145. }
  146.  
  147. - (int)positionForLineEndActual
  148. /* Not currently in use.  Looks for newline to find actual paragraph end.
  149.  */
  150. {
  151.     NXStream *s = [self stream];
  152.  
  153.     int pos;
  154.     int ch;
  155.     int max = [self textLength];
  156.     
  157.     if (spN.cp < 0) return 0; 
  158.     if (spN.cp > max) return max;
  159.  
  160.     NXSeek(s, spN.cp, NX_FROMSTART);
  161.     while (((ch = NXGetc(s)) != EOF) && (ch != '\n'));
  162.     pos = NXTell(s);
  163.     if (ch != EOF) pos--;
  164.     return pos;
  165. }
  166.  
  167. - (int)positionForLineEndVisual
  168. /* This uses the break array to find the visual line end.  
  169.  * However, it subtracts one from the position because of that behavior 
  170.  * of the Text object that makes the position at the end of one line 
  171.  * the same character position a the beginning of next line.  Seems to
  172.  * be no way to position the insertion point at the end of the line.
  173.  * Bummer.
  174.  */
  175. {
  176.     int lineLength;
  177.     int line;
  178.     
  179.     line = (spN.line /sizeof(NXLineDesc));
  180.     lineLength = theBreaks->breaks[line] & 0x3fff;
  181.     lineLength--; // Notice the hack....
  182.     return (spN.c1st + lineLength);
  183. }
  184.  
  185. - (int)positionForLineBeginVisual
  186. {
  187.     return (sp0.c1st);
  188. }
  189.  
  190. /** BIG FAT HAIRY NOTE
  191.  * This is how to change CTRL-A, CTRL-E, CTRL-K to use paragraph ends
  192.  * (actual newlines) instead of visual line breaks.  Have the position
  193.  * for line end methods call to the position for actual rather than
  194.  * visual.
  195.  */
  196.  
  197. - (int)positionForLineBegin
  198. {
  199.     return [self positionForLineBeginVisual];
  200. }
  201.  
  202. - (int)positionForLineEnd
  203. {
  204.     return [self positionForLineEndVisual];
  205.  
  206. }
  207.  
  208. - (int)nextPositionIfEmpty
  209. {
  210.      if (sp0.cp == spN.cp) 
  211.     return spN.cp + 1;
  212.     else
  213.     return spN.cp;
  214. }
  215.  
  216. /* This is my quick decision on what characters count as a word, and which
  217.  * don't.  The correct way to do this is to parse the ClickTable, but the
  218.  * documentation is so incredibly sparse on this one....
  219.  */
  220.  
  221. #define NORMAL_CHAR(ch) (((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) ||((ch >= '0') && (ch <= '9')) || (ch == '\'')|| (ch == '_'))
  222.  
  223.  
  224. - (int)positionForWordEnd
  225. {
  226.     NXStream *s = [self stream];
  227.  
  228.     int pos;
  229.     int ch;
  230.     int max = [self textLength];
  231.     
  232.     if (spN.cp < 0) return 0;     // boundary conditions?  Is this right idea?
  233.     if (spN.cp > max) return max;
  234.  
  235.     NXSeek(s, spN.cp, NX_FROMSTART);
  236.     while ((ch = NXGetc(s)) != EOF && !NORMAL_CHAR(ch)); // skip white space
  237.     while ((ch = NXGetc(s)) != EOF && NORMAL_CHAR(ch));    // jump normal chars
  238.     pos = NXTell(s);
  239.     if (ch != EOF) pos--;
  240.     return pos;
  241. }
  242.  
  243. - (int)positionForWordBegin
  244. {
  245.     NXStream *s = [self stream];
  246.  
  247.     int pos;
  248.     int ch;
  249.     int max = [self textLength];
  250.     
  251.     if (spN.cp < 0) return 0;     // boundary conditions?  Is this right idea?
  252.     if (spN.cp > max) return max;
  253.  
  254.     NXSeek(s, sp0.cp, NX_FROMSTART);
  255.     while ((ch = GetPrevious(s)) != EOF && !NORMAL_CHAR(ch)); // skip white space
  256.     while ((ch = GetPrevious(s)) != EOF && NORMAL_CHAR(ch)); // jump normal chars
  257.     pos = NXTell(s);
  258.     if (ch != EOF) pos++;
  259.     return pos;
  260. }
  261.  
  262. - (int) positionForDocumentEnd
  263. {
  264.      return [self textLength];
  265. }
  266.  
  267. - (int) positionForDocumentBegin
  268. {
  269.      return 0;
  270. }
  271.  
  272. - moveToPosition:(SEL)command
  273. {
  274.     int pos;
  275.     
  276.     pos = (int)[self perform:command];
  277.     [self setSel:pos :pos];
  278.     [self scrollSelToVisible];
  279.     return self;
  280. }
  281.  
  282. - deleteToPosition:(SEL)command
  283. /* Entry point for delete forward/backward word
  284.  */
  285. {
  286.     int pos;
  287.     int start,end;
  288.     
  289.     pos = (int)[self perform:command];
  290.     if (pos > spN.cp) 
  291.     {     // if position extends to the right
  292.         start = sp0.cp;
  293.     end = pos;
  294.     } 
  295.     else 
  296.     {        // else position extends to the left
  297.         start = pos;
  298.     end = spN.cp;
  299.     }
  300.     [self delete:start :end];
  301.     return self;
  302. }
  303.  
  304. - delete:(int)start :(int)end
  305. /* Entry point for all deletes done for Emacs bindings.  Turns off 
  306.  * autodisplay to avoid flicker and other unwanted drawing artifacts.
  307.  * Calls cut and uses the Pasteboard to implement yank.  It is possible
  308.  * to implement separate Emacs kill buffer, but it would be a bit of
  309.  * hassle, because you need a Change object to keep both the runs and
  310.  * the text that is yanked.  You can do it quick by storing only ASCII
  311.  * text, which is not a good idea.  (Actually, to be correct, this is
  312.  * all that Edit does, but who wants to use Edit for a role model?)
  313.  */
  314. {
  315.     if (end - start) 
  316.     {
  317.     [self setAutodisplay:NO];
  318.     [self setSel:start :end];
  319.     [self cut:self];
  320.     [[self setAutodisplay:YES] display];
  321.     }
  322.     return self;
  323. }
  324.  
  325.  
  326. - deleteToLineEnd
  327. /* Somewhat icky hack has to handle the special case for deleting at end 
  328.  * of line.  If in middle of line, don't delete the new line.  If at the 
  329.  * very end of the line, do delete the new line.
  330.  */
  331. {
  332.     int pos;
  333.     int start,end;
  334.     
  335.     pos = [self positionForLineEnd];
  336.     start = sp0.cp;
  337.     end = pos;
  338.     if (start == end) {// If already at end of line
  339.     int line;
  340.     int endsWithNewLine;
  341.     
  342.     line = (spN.line /sizeof(NXLineDesc));
  343.     endsWithNewLine = theBreaks->breaks[line] & 0x4000;
  344.  
  345.     if (endsWithNewLine) 
  346.         end++;
  347.     else  // Bail on case where at visual end of line, but no newline
  348.         return self;
  349.     }
  350.     [self delete:start :end];
  351.     return self;
  352. }
  353.  
  354.  
  355. - yank
  356. {
  357.     [self paste:self];
  358.     return self;
  359. }
  360.  
  361.  
  362. - (BOOL) emacsEvent:(NXEvent *)event
  363. {
  364.     SelectorItem *cur;
  365.     unsigned charCode = event->data.key.charCode;
  366.     
  367.     if (event->flags & NX_CONTROLMASK) 
  368.     {  
  369.         cur = emacsControlKeys;
  370.             
  371.     while (cur->charCode && (cur->charCode != charCode)) cur++;
  372.     if (cur->charCode) 
  373.     {
  374.         [self perform:cur->selector 
  375.             withSel:(cur->positionSelector? cur->positionSelector : 0)];
  376.         return YES;
  377.     }
  378.     }
  379.     if (event->flags & NX_ALTERNATEMASK) 
  380.     {  
  381.         cur = emacsMetaKeys;
  382.             
  383.     while (cur->charCode && (cur->charCode != charCode)) cur++;
  384.     if (cur->charCode) 
  385.     {
  386.         [self perform:cur->selector 
  387.             withSel:(cur->positionSelector? cur->positionSelector : 0)];
  388.         return YES;
  389.     }
  390.     }
  391.     return NO;
  392. }
  393.  
  394. - keyDown:(NXEvent *)event
  395. {
  396.     if ([self emacsEvent:event]) 
  397.     return self;
  398.     else
  399.     return [super keyDown:event];
  400. }
  401.  
  402. - (int)perform:(SEL)selector withSel:(SEL)helper 
  403. {
  404.     int   (*func)(id,SEL,SEL); 
  405.     
  406.     func = (int (*)(id,SEL,SEL))[self methodFor:selector];
  407.     return (* func)(self, selector, helper);
  408. }
  409.  
  410. - initFrame:(NXRect *)fRect
  411. {
  412.     NXRect r = *fRect;
  413.     [super initFrame:fRect];
  414.     [self setMonoFont:NO];
  415.     [self setBackgroundGray:NX_WHITE];
  416.     [self setOpaque:YES];
  417.     [self setCharFilter:(NXCharFilterFunc)emacsFilter];
  418.     [self notifyAncestorWhenFrameChanged: YES];
  419.     [self setVertResizable:YES];
  420.     [self setMinSize:&r.size];
  421.     r.size.height = 1.0e30;
  422.     [self setMaxSize:&r.size];
  423.     return self;
  424. }
  425.  
  426.  
  427. @end
  428.